using System.IO;
using System.Text;

namespace ColladaDotNet.Pipeline.MD3
{
	public class MD3Reader
	{
		public static MD3File Parse(FileStream s)
		{
			var start = s.Position;
			var res = new MD3File();
			using (var br = new BinaryReader(s, Encoding.ASCII))
			{
				res.Magic = "";
				res.Magic += br.ReadChar();
				res.Magic += br.ReadChar();
				res.Magic += br.ReadChar();
				res.Magic += br.ReadChar();

				if (res.Magic != "IDP3")
					throw new MD3FormatException("Magic should be IDP3");
				res.VERSION = br.ReadInt32();

				res.NAME = ReadString(br, 64);

				res.FLAGS = br.ReadInt32();// 	
				res.NUM_FRAMES = br.ReadInt32();//
				res.NUM_TAGS = br.ReadInt32();// 	
				res.NUM_SURFACES = br.ReadInt32();
				res.NUM_SKINS = br.ReadInt32();// 
				res.OFS_FRAMES = br.ReadInt32();//
				res.OFS_TAGS = br.ReadInt32();// 	
				res.OFS_SURFACES = br.ReadInt32();
				res.OFS_EOF = br.ReadInt32();// 	

				if (s.Position != res.OFS_FRAMES+start)
				{
					s.Position = res.OFS_FRAMES+start;
				}
				res.Frames = new Frame[res.NUM_FRAMES];
				for (int i = 0; i < res.NUM_FRAMES;++i )
				{
					res.Frames[i] = ReadFrame(br);
				}

				if (s.Position != res.OFS_SURFACES + start)
				{
					s.Position = res.OFS_SURFACES + start;
				}
				res.Surfaces = new Surface[res.NUM_SURFACES];
				for (int i = 0; i < res.NUM_SURFACES; ++i)
				{
					res.Surfaces[i] = ReadSurface(br);
				}
			}
			return res;
		}

		private static Surface ReadSurface(BinaryReader br)
		{
			var start = br.BaseStream.Position;

			var res = new Surface();
			res.IDENT = ReadString(br,4);// 	Magic number. As a string of 4 octets, reads "IDP3"; as unsigned little-endian 860898377 (0x33504449); as unsigned big-endian 1229213747 (0x49445033).
			if (res.IDENT != "IDP3")
				throw new MD3FormatException("Magic should be IDP3");

			res.NAME = ReadString(br, 64);// 	Name of Surface object. ASCII character string, NUL-terminated (C-style).
			res. 	FLAGS = br.ReadInt32();// 	 ???
			res. 	NUM_FRAMES = br.ReadInt32();// 	Number of animation frames. This should match NUM_FRAMES in the MD3 header.
			res. 	NUM_SHADERS = br.ReadInt32();// 	Number of Shader objects defined in this Surface, with a limit of MD3_MAX_SHADERS. Current value of MD3_MAX_SHADERS is 256.
			res. 	NUM_VERTS = br.ReadInt32();// 	Number of Vertex objects defined in this Surface, up to MD3_MAX_VERTS. Current value of MD3_MAX_VERTS is 4096.
			res. 	NUM_TRIANGLES = br.ReadInt32();// 	Number of Triangle objects defined in this Surface, maximum of MD3_MAX_TRIANGLES. Current value of MD3_MAX_TRIANGLES is 8192.
			res. 	OFS_TRIANGLES = br.ReadInt32();// 	Relative offset from SURFACE_START where the list of Triangle objects starts.
			res. 	OFS_SHADERS = br.ReadInt32();// 	Relative offset from SURFACE_START where the list of Shader objects starts.
			res. 	OFS_ST = br.ReadInt32();// 	Relative offset from SURFACE_START where the list of ST objects (s-t texture coordinates) starts.
			res. 	OFS_XYZNORMAL = br.ReadInt32();// 	Relative offset from SURFACE_START where the list of Vertex objects (X-Y-Z-N vertices) starts.
			res.	OFS_END = br.ReadInt32();// 	Relative offset from SURFACE_START to where the Surface object ends.

			if (start+res.OFS_TRIANGLES != br.BaseStream.Position)
			{
				br.BaseStream.Position = start + res.OFS_TRIANGLES;
			}
		
			res.Triangles = new Triangle[res.NUM_TRIANGLES];
			for (int i = 0; i < res.NUM_TRIANGLES; i++)
			{
				res.Triangles[i] = ReadTriangle(br);
			}
				

			if (start + res.OFS_SHADERS != br.BaseStream.Position)
			{
				br.BaseStream.Position = start + res.OFS_SHADERS;
			}
			res.Shaders = new Shader[res.NUM_SHADERS];
			for (int i = 0; i < res.NUM_SHADERS; i++)
			{
				res.Shaders[i] = ReadShader(br);
			}

			if (start + res.OFS_ST != br.BaseStream.Position)
			{
				br.BaseStream.Position = start + res.OFS_ST;
			}
			res.TexCoords = new TexCoord[res.NUM_VERTS];
			for (int i = 0; i < res.NUM_VERTS; i++)
			{
				res.TexCoords[i] = ReadTexCoord(br);
			}

			if (start+res.OFS_XYZNORMAL != br.BaseStream.Position)
			{
				br.BaseStream.Position = start + res.OFS_XYZNORMAL;
			}
			res.Verts = new Vertex[res.NUM_FRAMES][];
			for (int j = 0; j < res.NUM_FRAMES; j++)
			{
				res.Verts[j] = new Vertex[res.NUM_VERTS];
				for (int i = 0; i < res.NUM_VERTS; i++)
				{
					res.Verts[j][i] = ReadVertex(br);
				}

			}
			
			if (start+res.OFS_END != br.BaseStream.Position)
			{
				br.BaseStream.Position = start + res.OFS_END;
			}
			return res;
		}

		private static TexCoord ReadTexCoord(BinaryReader br)
		{
			var res = new TexCoord();
			res.ST = new float[] { br.ReadSingle(), br.ReadSingle() };
			return res;
		}

		private static Shader ReadShader(BinaryReader br)
		{
			var res = new Shader();
			res.NAME = ReadString(br, 64);
			res.SHADER_INDEX = br.ReadInt32();
			return res;
		}

		private static Vertex ReadVertex(BinaryReader br)
		{
			var res = new Vertex();
			res.COORD = new short[] { br.ReadInt16(), br.ReadInt16(), br.ReadInt16() };
			res.NORMAL = new byte[] { br.ReadByte(), br.ReadByte()};
			return res;
		}

		private static Triangle ReadTriangle(BinaryReader br)
		{
			var res = new Triangle();
			res.INDEXES = new int[] { br.ReadInt32(), br.ReadInt32(), br.ReadInt32() };
			return res;
		}

		private static Frame ReadFrame(BinaryReader br)
		{
			var res = new Frame();
			res.MIN_BOUNDS = new float[3]{br.ReadSingle(),br.ReadSingle(),br.ReadSingle()};// 	First corner of the bounding box.
			res.MAX_BOUNDS = new float[3] { br.ReadSingle(), br.ReadSingle(), br.ReadSingle() };// 	Second corner of the bounding box.
			res.LOCAL_ORIGIN = new float[3]{br.ReadSingle(),br.ReadSingle(),br.ReadSingle()};// 	Local origin, usually (0, 0, 0).
			res.RADIUS = br.ReadSingle();// 	Radius of bounding sphere.
			res.NAME = ReadString(br,16);// 	Name of Frame. ASCII character string, NUL-terminated (C-style)
			return res;
		}

		private static string ReadString(BinaryReader br, int len)
		{
			var NAME = string.Empty;
			int i = 0;
			for (i = 0; i < len; ++i)
			{
				var c = br.ReadChar();
				if (c == '\0')
				{
					++i;
					break;
				}
				NAME += c;
			}
			for (; i < len; ++i)
			{
				br.ReadChar();
			}
			return NAME;
		}
	}
}